---
title: "Argument and Return Value Validation"
sidebar_label: "Validation"
sidebar_position: 50
description: "Validate function arguments and return values for security"
---

import ConvexValues from "@site/docs/_convexValues.mdx";

import messagesTS from "!!raw-loader!@site/../demos/args-validation/convex/messages.ts";

Argument and return value validators ensure that
[queries](./query-functions.mdx), [mutations](./mutation-functions.mdx), and
[actions](./actions.mdx) are called with the correct types of arguments and
return the expected types of return values.

**This is important for security!** Without argument validation, a malicious
user can call your public functions with unexpected arguments and cause
surprising results. [TypeScript](/understanding/best-practices/typescript) alone
won't help because TypeScript types aren't present at runtime. We recommend
adding argument validation for all public functions in production apps. For
non-public functions that are not called by clients, we recommend
[internal functions](/functions/internal-functions.mdx) and optionally
validation.

**Example:**
[Argument Validation](https://github.com/get-convex/convex-demos/tree/main/args-validation)

## Adding validators

To add argument validation to your functions, pass an object with `args` and
`handler` properties to the `query`, `mutation` or `action` constructor. To add
return value validation, use the `returns` property in this object:

<TSAndJSSnippet
  title="convex/message.ts"
  sourceTS={messagesTS}
  sourceJS={messagesTS}
  snippet="mutation"
/>

If you define your function with an argument validator, there is no need to
include [TypeScript](/understanding/best-practices/typescript.mdx) type
annotations! The type of your function will be inferred automatically.
Similarly, if you define a return value validator, the return type of your
function will be inferred from the validator, and TypeScript will check that it
matches the inferred return type of the `handler` function.

Unlike TypeScript, validation for an object will throw if the object contains
properties that are not declared in the validator.

If the client supplies arguments not declared in `args`, or if the function
returns a value that does not match the validator declared in `returns`. This is
helpful to prevent bugs caused by mistyped names of arguments or returning more
data than intended to a client.

Even `args: {}` is a helpful use of validators because TypeScript will show an
error on the client if you try to pass any arguments to the function which
doesn't expect them.

## Supported types

All functions, both public and internal, can accept and return the following
data types. Each type has a corresponding validator that can be accessed on the
[`v`](/api/modules/values#v) object imported from `"convex/values"`.

The [database](/database.mdx) can store the exact same set of
[data types](/database/types.md).

Additionally you can also express type unions, literals, `any` types, and
optional fields.

### Convex values

<ConvexValues />

### Unions

You can describe fields that could be one of multiple types using `v.union`:

```typescript
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export default mutation({
  args: {
    stringOrNull: v.union(v.string(), v.null()),
  },
  handler: async (ctx, { stringOrNull }) => {
    //...
  },
});
```

For convenience, `v.nullable(foo)` is equivalent to `v.union(foo, v.null())`.

### Literals

Fields that are a constant can be expressed with `v.literal`. This is especially
useful when combined with unions:

```typescript
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export default mutation({
  args: {
    oneTwoOrThree: v.union(
      v.literal("one"),
      v.literal("two"),
      v.literal("three"),
    ),
  },
  handler: async (ctx, { oneTwoOrThree }) => {
    //...
  },
});
```

### Record objects

You can describe objects that map arbitrary keys to values with `v.record`:

```typescript
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export default mutation({
  args: {
    simpleMapping: v.record(v.string(), v.boolean()),
  },
  handler: async (ctx, { simpleMapping }) => {
    //...
  },
});
```

You can use other types of string validators for the keys:

```typescript
defineTable({
  userIdToValue: v.record(v.id("users"), v.boolean()),
});
```

Notes:

- This type corresponds to the
  [Record\<K,V\>](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)
  type in TypeScript.
- You cannot use string literals as a `record` key.
- Using `v.string()` as a `record` key validator will only allow ASCII
  characters.

### Any

Fields that could take on any value can be represented with `v.any()`:

```typescript
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export default mutation({
  args: {
    anyValue: v.any(),
  },
  handler: async (ctx, { anyValue }) => {
    //...
  },
});
```

This corresponds to the `any` type in TypeScript.

### Optional fields

You can describe optional fields by wrapping their type with `v.optional(...)`:

```typescript
import { mutation } from "./_generated/server";
import { v } from "convex/values";

export default mutation({
  args: {
    optionalString: v.optional(v.string()),
    optionalNumber: v.optional(v.number()),
  },
  handler: async (ctx, { optionalString, optionalNumber }) => {
    //...
  },
});
```

This corresponds to marking fields as optional with `?` in TypeScript.

## Extracting TypeScript types

The [`Infer`](/api/modules/values#infer) type allows you to turn validator calls
into TypeScript types. This can be useful to remove duplication between your
validators and TypeScript types:

```ts
import { mutation } from "./_generated/server";
import { Infer, v } from "convex/values";

const nestedObject = v.object({
  property: v.string(),
});

// Resolves to `{property: string}`.
export type NestedObject = Infer<typeof nestedObject>;

export default mutation({
  args: {
    nested: nestedObject,
  },
  handler: async (ctx, { nested }) => {
    //...
  },
});
```

### Reusing and extending validators

Validators can be defined once and shared between functions and table schemas.

```typescript
const statusValidator = v.union(v.literal("active"), v.literal("inactive"));

const userValidator = v.object({
  name: v.string(),
  email: v.email(),
  status: statusValidator,
  profileUrl: v.optional(v.string()),
});

const schema = defineSchema({
  users: defineTable(userValidator).index("by_email", ["email"]),
});
```

You can create new object validators from existing ones by adding or removing
fields using `.pick`, `.omit`, `.extend`, and `.partial` on object validators.

```typescript
// Creates a new validator with only the name and profileUrl fields.
const publicUser = userValidator.pick("name", "profileUrl");

// Creates a new validator with all fields except the specified fields.
const userWithoutStatus = userValidator.omit("status", "profileUrl");

// Creates a validator where all fields are optional.
// This is useful for validating patches to a document.
const userPatch = userWithoutStatus.partial();

// Creates a new validator adding system fields to the user validator.
const userDocument = userValidator.extend({
  _id: v.id("users"),
  _creationTime: v.number(),
});
```

Notes:

- Object validators don't allow extra properties, objects with properties that
  aren't specified will fail validation.
- Top-level table fields cannot start with `_` because they are reserved for
  system fields like `_id` and `_creationTime`.
